home *** CD-ROM | disk | FTP | other *** search
- /* ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1/GPL 2.0/LGPL 2.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is Lightning code.
- *
- * The Initial Developer of the Original Code is Oracle Corporation
- * Portions created by the Initial Developer are Copyright (C) 2005
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- * Mike Shaver <shaver@mozilla.org>
- * Vladimir Vukicevic <vladimir@pobox.com>
- * Dan Mosedale <dmose@mozilla.org>
- * Joey Minta <jminta@gmail.com>
- * Stefan Sitter <ssitter@googlemail.com>
- * Daniel Boelzle <daniel.boelzle@sun.com>
- * gekacheka@yahoo.com
- * richard@duif.net
- * Matthew Willis <mattwillis@gmail.com>
- * Markus Adrario <MarkusAdrario@web.de>
- * Philipp Kewisch <mozilla@kewis.ch>
- * Martin Schroeder <mschroeder@mozilla.x-home.org>
- *
- * Alternatively, the contents of this file may be used under the terms of
- * either the GNU General Public License Version 2 or later (the "GPL"), or
- * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- * in which case the provisions of the GPL or the LGPL are applicable instead
- * of those above. If you wish to allow use of your version of this file only
- * under the terms of either the GPL or the LGPL, and not to allow others to
- * use your version of this file under the terms of the MPL, indicate your
- * decision by deleting the provisions above and replace them with the notice
- * and other provisions required by the GPL or the LGPL. If you do not delete
- * the provisions above, a recipient may use your version of this file under
- * the terms of any one of the MPL, the GPL or the LGPL.
- *
- * ***** END LICENSE BLOCK ***** */
-
- // Agenda tree view, to display upcoming events, tasks, and reminders
- //
- // We track three periods of time for a segmented view:
- // - today: the current time until midnight
- // - tomorrow: midnight to midnight
- // - soon: end-of-tomorrow to end-of-one-week-from-today (midnight)
- //
- // Events (recurrences of events, really) are stored in per-period containers,
- // hung off of "period" objects. In addition, we build an array of the row-
- // representation we use for backing the tree display.
- //
- // The tree-view array (this.events) consists of the synthetic events for the time
- // periods, each one followed, if tree-expanded, by its collection of events. This
- // results in a this.events array like the following, if "Today" and "Soon" are
- // expanded:
- // [ synthetic("Today"),
- // occurrence("Today Event 1"),
- // occurrence("Today Event 2"),
- // synthetic("Tomorrow"),
- // synthetic("Soon"),
- // occurrence("Soon Event 1"),
- // occurrence("Soon Event 2") ]
- //
- // At window load, we connect the view to the tree and initiate a calendar query
- // to populate the event buckets. Once the query is complete, we sort each bucket
- // and then build the aggregate array described above.
- //
- // When calendar queries are refreshed (by a calendar being added/removed WRT the
- // current view, the user selecting a different filter, or some hidden manual-
- // refresh testing UI) the event buckets are emptied, and we add items as they
- // arrive.
- //
-
- function Synthetic(title, open)
- {
- this.title = title;
- this.open = open;
- this.events = [];
- }
-
- var agendaTreeView = {
- events: [],
- todayCount: 0,
- tomorrowCount: 0,
- soonCount: 0,
- prevRowCount: 0,
- mListener: null,
- initialized: false,
- refreshQueue: [],
- pendingRefresh: null
- };
-
- agendaTreeView.init =
- function initAgendaTree()
- {
- this.today = new Synthetic(ltnGetString("lightning", "agendaToday"), true);
- this.tomorrow = new Synthetic(ltnGetString("lightning", "agendaTomorrow"), false);
- this.soon = new Synthetic(ltnGetString("lightning", "agendaSoon"), false);
- this.periods = [this.today, this.tomorrow, this.soon];
- this.initialized = true;
- }
-
- agendaTreeView.addEvents =
- function addEvents(master)
- {
- this.events.push(master);
- if (master.open)
- this.events = this.events.concat(master.events);
- };
-
- agendaTreeView.rebuildEventsArray =
- function rebuildEventsArray()
- {
- this.events = [];
- this.addEvents(this.today);
- this.addEvents(this.tomorrow);
- this.addEvents(this.soon);
- };
-
- agendaTreeView.forceTreeRebuild =
- function forceTreeRebuild()
- {
- if (this.tree) {
- this.tree.view = this;
- }
- };
-
- agendaTreeView.rebuildAgendaView =
- function rebuildAgendaView(invalidate)
- {
- this.rebuildEventsArray();
- this.forceTreeRebuild();
- };
-
- agendaTreeView.__defineGetter__("rowCount",
- function get_rowCount()
- {
- return this.events.length;
- });
-
- agendaTreeView.isContainer =
- function isContainer(row)
- {
- return (this.events[row] instanceof Synthetic);
- };
-
- agendaTreeView.isContainerOpen =
- function isContainerOpen(row)
- {
- var open = this.events[row].open;
- return open;
- };
-
- agendaTreeView.isContainerEmpty =
- function isContainerEmpty(row)
- {
- if (this.events[row].events.length == 0)
- return true;
- return false;
- };
-
- agendaTreeView.setTree =
- function setTree(tree)
- {
- this.tree = tree;
- };
-
- agendaTreeView.showsToday =
- function showsToday()
- {
- return (sameDay(today(), this.today.start));
- };
-
- agendaTreeView.getCellText =
- function getCellText(row, column)
- {
- var dateFormatter = Components.classes["@mozilla.org/calendar/datetime-formatter;1"]
- .getService(Components.interfaces.calIDateTimeFormatter);
- // title column
- var event = this.events[row];
- if (column.id == "col-agenda-item") {
- if (event instanceof Synthetic) {
- if (this.showsToday()) {
- return event.title;
- }
- else {
- if (event == this.today) {
- return dateFormatter.formatDate(this.today.start);
- }
- else if (event == this.tomorrow) {
- return dateFormatter.formatDate(this.tomorrow.start);
- }
- else {
- var startString = new Object();
- var endString = new Object();
- dateFormatter.formatInterval(
- this.soon.start, this.soon.end, startString, endString);
- var dateString = startString.value + " - " + endString.value;
- return dateString;
- }
- }
- }
- else {
- return event.title;
- }
- }
- else {
- if (event instanceof Synthetic) {
- return "";
- } else {
- var start = event.startDate || event.dueDate || event.entryDate;
- start = start.getInTimezone(calendarDefaultTimezone());
- if (start.compare(this.tomorrow.end) == -1) {
- // time only for events on today and tomorrow
- return dateFormatter.formatTime(start);
- }
- else {
- return dateFormatter.formatDateTime(start);
- }
- }
- }
- };
-
- agendaTreeView.getLevel =
- function getLevel(row)
- {
- if (this.isContainer(row))
- return 0;
- return 1;
- };
-
- agendaTreeView.isSorted =
- function isSorted() { return false; };
-
- agendaTreeView.isEditable =
- function isEditable(row, column) { return false; };
-
- agendaTreeView.isSeparator =
- function isSeparator(row) { return false; };
-
- agendaTreeView.getImageSrc =
- function getImageSrc(row, column) { return null; };
-
- agendaTreeView.getCellProperties =
- function getCellProperties(row, column, props) {
- if (!this.isContainer(row)) {
- return;
- }
-
- var atomSvc = Components.classes["@mozilla.org/atom-service;1"]
- .getService(Components.interfaces.nsIAtomService);
- props.AppendElement(atomSvc.getAtom("agenda-header"));
- };
-
- agendaTreeView.getRowProperties =
- function getRowProperties(row) { return null; };
-
- agendaTreeView.getColumnProperties =
- function getColumnProperties(column) { return null; };
-
- agendaTreeView.cycleHeader =
- function cycleHeader(header)
- {
- this.refreshCalendarQuery(); // temporary hackishness
- this.rebuildAgendaView();
- this.forceTreeRebuild();
- };
-
- agendaTreeView.getParentIndex =
- function getParentIndex(row)
- {
- if (this.isContainer(row))
- return -1;
- var i = row - 1;
- do {
- if (this.events[i] instanceof Synthetic)
- return i;
- i--;
- } while (i != -1);
- throw "no parent for row " + row + "?";
- };
-
- agendaTreeView.toggleOpenState =
- function toggleOpenState(row)
- {
- if (!this.isContainer(row))
- throw "toggling open state on non-container row " + row + "?";
- var header = this.events[row];
- if (!("open") in header)
- throw "no open state found on container row " + row + "?";
- header.open = !header.open;
- this.rebuildAgendaView(); // reconstruct the visible row set
- this.forceTreeRebuild();
- };
-
- agendaTreeView.hasNextSibling =
- function hasNextSibling(row, afterIndex)
- {
- };
-
- agendaTreeView.findPeriodForItem =
- function findPeriodForItem(item)
- {
- var start = item.startDate || item.entryDate || item.dueDate;
- if (!start)
- return null;
- start = start.getInTimezone(calendarDefaultTimezone());
- if (start.compare(this.today.end) == -1)
- return this.today;
-
- if (start.compare(this.tomorrow.end) == -1)
- return this.tomorrow;
-
- if (start.compare(this.soon.end) == -1)
- return this.soon;
-
-
- return null;
- };
-
- agendaTreeView.addItem =
- function addItem(item)
- {
- var when = this.findPeriodForItem(item);
- if (!when) {
- return;
- }
-
- // Prevent showing items with the same id but on multiple calendars from
- // appearing multiple times in the agenda.
- var dupe;
- for (var i = 0; i < when.events.length; i++) {
- if (item.id == when.events[i].id) {
- when.events.splice(i, 1, item);
- dupe = true;
- return;
- }
- dupe = false;
- }
- if (!dupe) {
- when.events.push(item);
- }
- this.calendarUpdateComplete();
- };
-
- agendaTreeView.onDoubleClick =
- function agendaDoubleClick(event)
- {
- // We only care about left-clicks
- if (event.button != 0)
- return;
-
- // Find the row clicked on, and the corresponding event
- var tree = document.getElementById("agenda-tree");
- var row = tree.treeBoxObject.getRowAt(event.clientX, event.clientY);
- var calendar = getSelectedCalendar();
- var calEvent = this.events[row];
-
- if (!calEvent) { // Clicked in empty space, just create a new event
- createEventWithDialog(calendar, this.today.start);
- return;
- }
- if (!this.isContainer(row)) { // Clicked on a task/event, edit it
- var eventToEdit = getOccurrenceOrParent(calEvent);
- modifyEventWithDialog(eventToEdit);
- } else { // Clicked on a container, create an event that day
- if (calEvent == this.today) {
- createEventWithDialog(calendar, this.today.start);
- } else {
- var tom = this.today.start.clone();
- var offset = (calEvent == this.tomorrow) ? 1 : 2;
- tom.day += offset;
- createEventWithDialog(calendar, tom);
- }
- }
- }
-
- agendaTreeView.onKeyPress =
- function onKeyPress(event)
- {
- const kKE = Components.interfaces.nsIDOMKeyEvent;
- switch(event.keyCode) {
- case kKE.DOM_VK_DELETE:
- document.getElementById('agenda_delete_event_command').doCommand();
- event.stopPropagation();
- event.preventDefault();
- break;
- }
- };
-
- /**
- * Delete the current selected item with focus from the Agenda- list
- */
- agendaTreeView.deleteEvent =
- function deleteAgendaEvent(aDoNotConfirm)
- {
- var selectedItems = new Array();
- var tree = document.getElementById("agenda-tree");
- var start = new Object();
- var end = new Object();
- var numRanges = tree.view.selection.getRangeCount();
- var agendaItem;
- for (var t = 0; t < numRanges; t++) {
- tree.view.selection.getRangeAt(t, start, end);
- for (var v = start.value; v <= end.value; v++) {
- selectedItems.push(this.events[v]);
- }
- }
- calendarViewController.deleteOccurrences(selectedItems.length,
- selectedItems,
- false,
- aDoNotConfirm);
- };
-
-
- agendaTreeView.deleteItem =
- function deleteItem(item)
- {
- var when = this.findPeriodForItem(item);
- if (!when) {
- return;
- }
-
- when.events = when.events.filter(function (e) {
- if (e.id != item.id)
- return true;
- if (e.recurrenceId && item.recurrenceId &&
- e.recurrenceId.compare(item.recurrenceId) != 0)
- return true;
- return false;
- });
- this.rebuildAgendaView(true);
- };
-
- agendaTreeView.calendarUpdateComplete =
- function calendarUpdateComplete()
- {
- [this.today, this.tomorrow, this.soon].forEach(function(when) {
- function compare(a, b) {
- // The assumption is that tasks having a dueDate should be added
- // to the agenda based on _that_, rather than entryDate, but tasks
- // with an entryDate but no dueDate shouldn't be left out.
- var ad = a.startDate || a.dueDate || a.entryDate;
- var bd = b.startDate || b.dueDate || b.entryDate;
- return ad.compare(bd);
- }
- when.events.sort(compare);
- });
- this.rebuildAgendaView(true);
- };
-
- agendaTreeView.calendarOpListener =
- {
- agendaTreeView: agendaTreeView
- };
-
- agendaTreeView.calendarOpListener.onOperationComplete =
- function listener_onOperationComplete(calendar, status, optype, id,
- detail)
- {
- this.agendaTreeView.calendarUpdateComplete();
-
- // signal that the current operation finished.
- this.agendaTreeView.pendingRefresh = null;
-
- // immediately start the next job on the queue.
- this.agendaTreeView.popRefreshQueue();
- };
-
- agendaTreeView.calendarOpListener.onGetResult =
- function listener_onGetResult(calendar, status, itemtype, detail, count, items)
- {
- if (!Components.isSuccessCode(status))
- return;
-
- items.forEach(this.agendaTreeView.addItem, this.agendaTreeView);
- };
-
- agendaTreeView.popRefreshQueue =
- function popRefreshQueue()
- {
- var pendingRefresh = this.pendingRefresh;
- if (pendingRefresh) {
- if (pendingRefresh instanceof Components.interfaces.calIOperation) {
- this.pendingRefresh = null;
- pendingRefresh.cancel(null);
- } else {
- return;
- }
- }
-
- var refreshJob = this.refreshQueue.pop();
- if (!refreshJob) {
- return;
- }
-
- var filter = this.calendar.ITEM_FILTER_CLASS_OCCURRENCES;
- if (document.getElementById("hide-completed-checkbox").checked) {
- filter |= this.calendar.ITEM_FILTER_COMPLETED_NO;
- } else {
- filter |= this.calendar.ITEM_FILTER_COMPLETED_ALL;
- }
-
- if (!this.filterType)
- this.filterType = 'all';
- switch (this.filterType) {
- case 'all':
- filter |= this.calendar.ITEM_FILTER_TYPE_EVENT |
- this.calendar.ITEM_FILTER_TYPE_TODO;
- break;
- case 'events':
- filter |= this.calendar.ITEM_FILTER_TYPE_EVENT;
- break;
- case 'tasks':
- filter |= this.calendar.ITEM_FILTER_TYPE_TODO;
- break;
- }
-
- this.pendingRefresh = true;
- this.periods.forEach(function (p) { p.events = []; });
- pendingRefresh = this.calendar.getItems(filter, 0, this.today.start, this.soon.end,
- this.calendarOpListener);
- if (pendingRefresh && pendingRefresh.isPending) { // support for calIOperation
- this.pendingRefresh = pendingRefresh;
- }
- };
-
- agendaTreeView.refreshCalendarQuery =
- function refreshCalendarQuery()
- {
- var refreshJob = {};
- this.refreshQueue.push(refreshJob);
- this.popRefreshQueue();
- };
-
- agendaTreeView.updateFilter =
- function updateAgendaFilter(menulist) {
- this.filterType = menulist.selectedItem.value;
- this.refreshCalendarQuery();
- return;
- };
-
- agendaTreeView.refreshPeriodDates =
- function refreshPeriodDates(d)
- {
- // Today: now until midnight of tonight
- this.today.start = d.clone();
- d.hour = d.minute = d.second = 0;
- d.day++;
- this.today.end = d.clone();
-
- // Tomorrow: midnight of next day to +24 hrs
- this.tomorrow.start = d.clone();
- d.day++;
- this.tomorrow.end = d.clone();
-
- // Soon: end of tomorrow to 6 six days later (remainder of the week period)
- this.soon.start = d.clone();
- d.day += 6;
- this.soon.end = d.clone();
-
- this.refreshCalendarQuery();
- };
-
- agendaTreeView.calendarObserver = {
- agendaTreeView: agendaTreeView
- };
-
- agendaTreeView.calendarObserver.QueryInterface = function agenda_QI(aIID) {
- if (!aIID.equals(Components.interfaces.calIObserver) &&
- !aIID.equals(Components.interfaces.calICompositeObserver) &&
- !aIID.equals(Components.interfaces.nsISupports)) {
- throw Components.results.NS_ERROR_NO_INTERFACE;
- }
- return this;
- };
-
- // calIObserver:
- agendaTreeView.calendarObserver.onStartBatch = function agenda_onBatchStart() {
- this.mBatchCount++;
- };
- agendaTreeView.calendarObserver.onEndBatch = function() {
- this.mBatchCount--;
- if (this.mBatchCount == 0) {
- // Rebuild everything
- this.agendaTreeView.refreshCalendarQuery();
- }
- };
- agendaTreeView.calendarObserver.onLoad = function(calendar) {
- this.agendaTreeView.refreshCalendarQuery();
- };
-
- agendaTreeView.calendarObserver.onAddItem =
- function observer_onAddItem(item)
- {
- if (this.mBatchCount) {
- return;
- }
-
- if (isToDo(item)) {
- var hideCompleted = document.getElementById("hide-completed-checkbox").checked;
- if (item.isCompleted && hideCompleted) {
- return;
- }
- }
-
- var occs = item.getOccurrencesBetween(this.agendaTreeView.today.start,
- this.agendaTreeView.soon.end, {});
- occs.forEach(this.agendaTreeView.addItem, this.agendaTreeView);
- this.agendaTreeView.rebuildAgendaView();
- };
-
- agendaTreeView.calendarObserver.onDeleteItem =
- function observer_onDeleteItem(item, rebuildFlag)
- {
- if (this.mBatchCount) {
- return;
- }
- var queryStart = this.agendaTreeView.today.start.clone();
- queryStart.hour = 0;
- queryStart.minute = 0;
- queryStart.second = 0;
- var occs = item.getOccurrencesBetween(queryStart,
- this.agendaTreeView.soon.end, {});
- occs.forEach(this.agendaTreeView.deleteItem, this.agendaTreeView);
- if (rebuildFlag != "no-rebuild")
- this.agendaTreeView.rebuildAgendaView();
- };
-
- agendaTreeView.calendarObserver.onModifyItem =
- function observer_onModifyItem(newItem, oldItem)
- {
- if (this.mBatchCount) {
- return;
- }
- this.onDeleteItem(oldItem, "no-rebuild");
-
- if (isToDo(newItem)) {
- var hideCompleted = document.getElementById("hide-completed-checkbox").checked;
- if (newItem.isCompleted && hideCompleted) {
- return;
- }
- }
- this.onAddItem(newItem);
- };
-
- agendaTreeView.calendarObserver.onError = function(errno, msg) {};
-
- agendaTreeView.calendarObserver.onCalendarAdded =
- function agenda_calAdd(aCalendar) {
- this.agendaTreeView.refreshCalendarQuery();
- };
-
- agendaTreeView.calendarObserver.onCalendarRemoved =
- function agenda_calRemove(aCalendar) {
- this.agendaTreeView.refreshCalendarQuery();
- };
-
- agendaTreeView.calendarObserver.onDefaultCalendarChanged = function(aCalendar) {
- };
-
- agendaTreeView.setCalendar =
- function setCalendar(calendar)
- {
- if (this.calendar)
- this.calendar.removeObserver(this.calendarObserver);
- this.calendar = calendar;
- calendar.addObserver(this.calendarObserver);
- this.init();
- };
-
-
- agendaTreeView.addListener =
- function addListener(aListener) {
- this.mListener = aListener;
- }
-
- function setAgendaTreeView()
- {
- agendaTreeView.setCalendar(getCompositeCalendar());
- document.getElementById("agenda-tree").view = agendaTreeView;
- if (agendaTreeView.mListener){
- agendaTreeView.mListener.updatePeriod();
- }
- }
-
- function sameDay(date1, date2) {
- if (date1 && date2) {
- if ((date1.day == date2.day) &&
- (date1.month == date2.month) &&
- (date1.year == date2.year)) {
- return true;
- }
- }
- return false;
- }
-
- window.addEventListener("load", setAgendaTreeView, false);
-